/*
* ========== Copyright Header Begin ==========================================
* 
* OpenSPARC T1 Processor File: parser.c
* Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
* 
* The above named program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License version 2 as published by the Free Software Foundation.
* 
* The above named program is distributed in the hope that it will be 
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
* 
* You should have received a copy of the GNU General Public
* License along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
* 
* ========== Copyright Header End ============================================
*/
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"@(#)parser.c	1.27	06/06/12 SMI"

#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <dlfcn.h>
#include <link.h>
#include <string.h>

#include "basics.h"
#include "fatal.h"
#include "strutil.h"
#include "allocate.h"
#include "simcore.h"
#include "config.h"
#include "options.h"
#include "fileutil.h"
#include "lexer.h"

#ifndef OPENSPARC /* { */
#include "sam_dev.h"
#endif	/* } */

#define	DBGLP(s)	do { s } while (0)

#define	MAX_TRIES	4

extern void lex_fatal(char * str, ...);

	/*
	 * Parse the top-level config file.
	 * Essentially this is restricted to the systems
	 * directive in the config file
	 */



static void parse_proc(domain_t * domainp);
static void parse_device(domain_t * domainp);
static void parse_addressmap(domain_t * domainp);
static void parse_domain(domain_t * domainp);
static void parse_domains(system_t * systemp);
static void parse_systems();
void *dlopen_lib(char *mdlnamep, char *symname, char *libname);

#ifndef OPENSPARC	/* { */
static void parse_mmi_device(domain_t * domainp);
#endif /* } ifndef OPENSPARC */

proc_type_t * find_proc_type(char * procname);
dev_type_t * find_dev_type(char * devname);
extern void sam_init_device(char *dev_namep, config_dev_t *config_devp);
extern dev_type_t *sam_load_device(config_dev_t *config_devp, tpaddr_t *baseaddr, tpaddr_t *topaddr);

target_config_t target_config;	/* define here ? */

	/* list of dev_type_t pointers to dl'd devices */
LIST_DEF( dev_cache, dev_type_t);

	/* list of proc_type_t pointers to dl'd processors */
LIST_DEF( proc_cache, proc_type_t);


	/*
	 * Setup empty configuration prior to config parsing
	 */

void init_target_config()
{
	LIST_INIT(target_config.systems, system_t);

		/* Init list of loaded device libraries */

	LIST_INIT(dev_cache, dev_type_t);

		/* Init list of loaded processor libraries */

	LIST_INIT(proc_cache, proc_type_t);
}





	/*
	 * This function calls into the lexer to
	 * parse the config and state files.
	 */

void parse_config_file()
{
	char tempfilep[64];
	char buffer[8192];	/* big space */
	int count;
	int res;
	FILE * fp;

	/*
	 * First step is to run the C pre-processor
	 * over the config file.
	 * Output from the pre-processor, is piped
	 * directly into the lexer.
	 * This is complicated because the pre-processor also
	 * outputs errors, and may fail.
	 */

	for (count=0; count<MAX_TRIES; count++) {
		sprintf(tempfilep, "/tmp/sim.cfg.%d.%02d", (int)getpid(), count);
		if (!file_exists(tempfilep)) break;
	}
	if (count == 5) fatal("Unable to create a temporary file for config pre-processing");

#if ERROR_INJECTION
	if (options.cpp_optionsp == NULL) {
		options.cpp_optionsp = " -DERROR_INJECTION";
	} else {
		strcat(options.cpp_optionsp," -DERROR_INJECTION");
	}
#endif
	errno = 0;
	sprintf(buffer,"cpp %s %s > %s",
		options.cpp_optionsp!=(char*)0 ? options.cpp_optionsp : "",
		options.config_filep, tempfilep);

	do {
DBG(		printf("system(%s)\n", buffer); );
		res = system(buffer);
	} while (res==-1 && (errno==EAGAIN || errno==EINTR));
	if (res == -1) fatal("Failed trying to pre-process config file %s\n", options.config_filep);

	PRINTF(("Exit status %d\n", res));

	fp = fopen_check(tempfilep, "r");
	init_lexer(options.config_filep, fp, tempfilep);

	parse_systems();

	fclose(fp);

	unlink(tempfilep);	/* clean up - remove temp file */



	if (options.verbose)
	    dump_config(stdout);
}



	/*
	 * Top level of parser ... look for one or more
	 * system directives.
	 */


void parse_systems()
{
	LIST_INIT( target_config.systems, system_t );

	do {
		system_t * systemp;
		lexer_tok_t tok;
		int idx;

		tok = lex_get_token();
		if (tok == T_EOF) break;
		if (tok != T_Token || !streq(lex.strp,"system"))
			lex_fatal("system definition expected");

		lex_get(T_String);

		idx = target_config.systems.count;
		systemp = LIST_ADD( target_config.systems, system_t );
		systemp->namep = Xstrdup(lex.strp);
		systemp->idx = idx;

		lex_get(T_L_Brace);

		parse_domains(systemp);

		lex_get(T_R_Brace);

	} while (1);

	if (target_config.systems.count == 0) lex_fatal("at least one system expected");
}



	/*
	 * Read one or more domains into the specified system
	 */

void parse_domains(system_t * systemp)
{
	LIST_INIT(systemp->domains, domain_t);

	do {
		domain_t * domainp;
		lexer_tok_t tok;
		int idx;

		tok = lex_get_token();
		if (tok == T_EOF) lex_fatal("unexpected EOF");
		if (tok == T_R_Brace) break;
		if (tok != T_Token || !streq(lex.strp,"domain"))
			lex_fatal("domain definition expected");

		idx = systemp->domains.count;
		domainp = LIST_ADD( systemp->domains, domain_t );
		domainp->idx = idx;

		lex_get(T_L_Brace);

		parse_domain(domainp);

		lex_get(T_R_Brace);
	} while (1);

	if (systemp->domains.count == 0) lex_fatal("At least one domain expected for system \"%s\"", systemp->namep);

	lex_unget();
}



	/*
	 * OK, the more complicated function
	 * to determine and connect the different
	 * components of a domain.
	 */

void parse_domain(domain_t * domainp)
{
	LIST_INIT( domainp->procs, config_proc_t );
	domainp->device.count = 0;
	domainp->device.listp = (config_dev_t *)0;

	do {
		lexer_tok_t tok;

		tok = lex_get_token();
		if (tok == T_EOF) lex_fatal("unexpected EOF within domain defn");
		if (tok == T_R_Brace) break;
		if (tok != T_Token) goto fail;

		if (streq(lex.strp,"sysclkfreq")) {
			domainp->sysclkfreq = parse_number_assign();
		} else
		if (streq(lex.strp,"processor")) {
			parse_proc(domainp);
		} else
		if (streq(lex.strp,"addressmap")) {
			parse_addressmap(domainp);
		} else {
fail:
			lex_fatal("domain specific token expected");
		}

	} while (1);

	lex_unget();

	if (domainp->sysclkfreq == 0) lex_fatal("sysclkfreq not specified in domain definition");
}






	/*
	 * Handle the proc "name" wrapper around a processor definition.
	 * This enables us to select the correct processor handler
	 * then call into that handler to complete the parsing
	 * of this specific processor.
	 */

void parse_proc(domain_t * domainp)
{
	proc_type_t * proc_typep;
	config_proc_t * config_procp;

	lex_get(T_String);		/* get the name of the proc type */

	proc_typep = find_proc_type(lex.strp);
	if (proc_typep == (proc_type_t *)0) lex_fatal("unknown processor type \"%s\"",lex.strp);

		/*
		 * If the processor type module has not been loaded and
		 * initialised .. do so now
		 */
	if (!proc_typep->flag_initialised) {
		if (!proc_typep->init_module(proc_typep)) fatal("Failed initialising processor module %s", proc_typep->proc_type_namep);
	}

	lex_get(T_L_Brace);

		/* OK, allocate the proc entry */
	config_procp = LIST_ADD( domainp->procs, config_proc_t );

	config_procp->proc_id = domainp->procs.count -1;
	config_procp->proc_typep = proc_typep;
	config_procp->domainp = domainp;

		/* based on the proc type we call the proc-dependent parser */
	proc_typep->parse_proc(domainp, config_procp);

		/* finish up cleanly */

	lex_get(T_R_Brace);
}







	/*
	 * This function (eventually) parses the address map
	 * for each domain. Basically, this creates the devices
	 * that are attached directly to the domain memory bus.
	 *
	 */

void parse_addressmap(domain_t * domainp)
{
	lexer_tok_t tok;

	lex_get(T_L_Brace);

		/* for now - just swallow until the } */
	while (1) {
		tok = lex_get_token();
		if (tok == T_EOF) lex_fatal("unexpected end of file");
		if (tok == T_R_Brace) break;

		if (tok != T_Token) lex_fatal("addressmap element expected");

		if (streq(lex.strp, "device")) {
			parse_device(domainp);
#ifndef OPENSPARC	/* { */
		} else if (streq(lex.strp, "mmi_device")) {
			parse_mmi_device(domainp);
#endif			/* } ifndef OPENSPARC */
		} else {
			lex_fatal("unknown/unexpected token %s in addressmap", lex.strp);
		}

	} while (tok != T_EOF && tok != T_R_Brace);
	lex_unget();

		/* finish up cleanly */
	lex_get(T_R_Brace);
}






	/*
	 * having decided the address map entry is a device,
	 * parse its name, base, and extent, then drop into the
	 * device's own parsing routine.
	 */

void parse_device(domain_t * domainp)
{
	config_dev_t * config_devp, *overlapp;
	config_addr_t *config_addrp;
	dev_type_t * dev_typep;
	bool_t is_size;
	lexer_tok_t tok;

	/*
	 * We've found a device name so check whether we have a library
	 * for this dev_type.
	 */
	lex_get(T_String);
	dev_typep = find_dev_type(lex.strp);
	if (dev_typep == (dev_type_t *)0) lex_fatal("unknown dev type \"%s\"",lex.strp);
		/* OK, allocate the proc entry */
	config_devp = Xmalloc( sizeof(config_dev_t) );

	config_devp->is_implied = false;	/* this is a real device */
	config_devp->dev_typep = dev_typep;
	config_devp->devp = (void*)0;		/* gets allocated in the dev parser */

	do {
		tpaddr_t baseaddr, topaddr;

		lex_get(T_Number);
		baseaddr = lex.val;

		is_size = false;
		tok = lex_get_token();
		if (tok == T_Plus) is_size = true; else lex_unget();

		lex_get(T_Number);
		topaddr = lex.val;
		if (is_size)
			topaddr += baseaddr;

		if (topaddr <= baseaddr)
			lex_fatal("top address <= base address with device %s",
				  config_devp->dev_typep->dev_type_namep);

		insert_domain_address(domainp, config_devp, baseaddr, topaddr);

	} while (lex_get_token() == T_Comma);

	lex_unget();

	/* Insert device into the domain */
	overlapp = insert_domain_device(domainp, config_devp);

	if (overlapp != NULL) {
		lex_fatal("device \"%s\" @ 0x%llx overlaps with device \"%s\" @ 0x%llx",
			overlapp->dev_typep->dev_type_namep,
			overlapp->addrp->baseaddr,
			config_devp->dev_typep->dev_type_namep,
			config_devp->addrp->baseaddr);
	}

		/* based on the dev type we call the dev-dependent parser */
	dev_typep->parse_dev(config_devp);
}



#ifndef OPENSPARC	/* { */
/*
 * Parse MMI device
 */
void parse_mmi_device(domain_t * domainp)
{
	config_dev_t * config_devp, *overlapp;
	config_addr_t *config_addrp;
	dev_type_t * dev_typep;
	tpaddr_t baseaddr, topaddr;
	lexer_tok_t tok;

	config_devp = Xmalloc( sizeof(config_dev_t) );

	/*
	 * parse to get the MMI device name
	 */
	lex_get(T_String);
	if (lex.strp == NULL)
		lex_fatal("missing MMI device name in parse_mmi_device");

	sam_init_device(lex.strp, config_devp);

	/* 
	 * finish the parsing
	 */
	config_devp->dev_typep->parse_dev(config_devp);

	/*
	 * load MMI SAM device module
	 */
	dev_typep = sam_load_device(config_devp, &baseaddr, &topaddr);
	if (dev_typep == (dev_type_t *)0) lex_fatal("unknown dev type \"%s\"",lex.strp);
		
	/* 
	 * Found a matching library, add it to the loaded list.
	 * 
	 * NOTE: We do not dlclose() the libarary here as we know it will be
	 * needed later on when the functions in the dev_typep are accessed.
	 */
	LIST_ADD_PTR(dev_cache, dev_type_t , dev_typep);

	if (baseaddr != NOADDR) {
		insert_domain_address(domainp, config_devp, baseaddr, topaddr);

		overlapp = insert_domain_device(domainp, config_devp);

		if (overlapp != NULL) {
			lex_fatal("device \"%s\" @ 0x%llx overlaps with device \"%s\" @ 0x%llx",
				overlapp->dev_typep->dev_type_namep,
				overlapp->addrp->baseaddr,
				config_devp->dev_typep->dev_type_namep,
				config_devp->addrp->baseaddr);
		}
	}

}

#endif	/* } ifndef OPENSPARC */

	/*
	 * Support functions for other parser codes
	 */

	/*
	 * Parse a number assignment:
	 * e.g.   0x40000 ;
	 */

uint64_t parse_number_assign()
{
	uint64_t val;

	lex_get(T_Number);
	val = lex.val;
	lex_get(T_S_Colon);

	return val;
}

/*
 * dlopen a lib["mdlnamep"].so in the plugins/ dir or in the
 * options.libpath search path and locate the symbol "symname".
 * Return a handle to correct library.
 *
 */
void *dlopen_lib(char *mdlnamep, char *symname, char *libname)
{
	void	*dlh = NULL, *fptr = NULL;
	int	idx;

	for (idx=0; idx<options.libpath.count; idx++) {
		char * pathp;

		pathp = LIST_ENTRY(options.libpath, idx);

		sprintf(libname, "%s/lib%s.so", pathp, mdlnamep);

DBGLP(		PRINTF(("Loading the [%s] module - %s\n", mdlnamep, libname)); );

		dlh = dlopen(libname, RTLD_NOW);
		if (NULL == dlh) {
			PRINTF(("\tError opening [%s]\n\t%s\n",
			    mdlnamep, dlerror() ));
			continue; 
		}
DBGLP(		PRINTF(("\tFound and opened [%s]\n", libname)); );

		fptr = dlsym(dlh, symname);
		if (NULL == fptr) {
			printf("\t[%s]\n", dlerror());
			continue; 
		}  

		/*
		 * We've found a library in the libpath that we can open and
		 * has the correct symbols so we can just return a handle
		 * to it. 
		 */
		return fptr;
	} 

		/* Try what ever compiled in default paths are available */

	sprintf(libname, "lib%s.so", mdlnamep);

DBGLP(	PRINTF(("Loading the [%s] module - %s\n", mdlnamep, libname)); );

	dlh = dlopen(libname, RTLD_NOW);
	if (NULL == dlh) {
		printf("\tError opening [%s]\n\t%s\n", mdlnamep, dlerror());
		return NULL;
	}
DBGLP(	PRINTF(("\tFound and opened [%s]\n", libname)); );

	fptr = dlsym(dlh, symname);
	if (NULL == fptr) {
		printf("\t[%s]\n", dlerror());
		return NULL;
	}  

	return fptr;
}


/*
 * Call dlopen_lib() to load the device module lib["devnamep"].so in 
 * the plugins/ dir. If a matching library is found:
 *
 *  - use dlsym() to look for it's dev_type_t structure
 *  - check the dev_type_t name value matches the "devnamep"
 *  - add the matching library to the loaded list
 *  - return a pointer to the dev_type_t for this device
 */
dev_type_t * find_dev_type(char * devnamep)
{
	char		symname[BUFSIZ], libname[BUFSIZ];
	dev_type_t	*dev_typep = NULL;
	int		idx;

	if ( devnamep == NULL)
		return (dev_type_t *)0;

	/*
	 * First look through the cached list of device
	 * modules we've already opened.
	 */
	for (idx=0; idx<dev_cache.count; idx++) {
		dev_typep = LIST_ENTRY(dev_cache, idx);

		if (streq(dev_typep->dev_type_namep, devnamep))
			return(dev_typep);
	}

	/*
	 * Dlopen the device module library
	 */
	sprintf(symname, "dev_type_%s", devnamep);

	dev_typep = (dev_type_t *)dlopen_lib(devnamep, symname, libname);
	if (dev_typep == NULL)
		return (dev_type_t *)0;

	/*
	 * Make sure the name in the dev_type_name matches the device 
	 */
	if (!streq(dev_typep->dev_type_namep, devnamep)) {
		printf("\nERROR: name does not match. %s != %s in %s:%s\n",
		       dev_typep->dev_type_namep, devnamep, libname, symname);
		return (dev_type_t *)0;
	}
		
	/* 
	 * Found a matching library, add it to the loaded list.
	 * 
	 * NOTE: We do not dlclose() the libarary here as we know it will be
	 * needed later on when the functions in the dev_typep are accessed.
	 */
	LIST_ADD_PTR(dev_cache, dev_type_t , dev_typep);

	return(dev_typep);
}


/*
 * Call dlopen_lib() to load the proc module lib["procnamep"].so in 
 * the plugins/ dir. If a matching library is found:
 *
 *  - use dlsym() to look for it's proc_type_t structure
 *  - check the proc_type_t name value matches the "procnamep"
 *  - add the matching library to the loaded list
 *  - return a pointer to the proc_type_t for this proc
 */
proc_type_t * find_proc_type(char * procnamep)
{
	char		symname[BUFSIZ], libname[BUFSIZ];
	proc_type_t	*proc_typep = NULL;
	int		idx;


	if ( procnamep == NULL)
		return (proc_type_t *)0;

	/* First look through the cached list of processor
	 * modules we've already opened.
	 */

	for (idx=0; idx<proc_cache.count; idx++) {
		proc_typep = LIST_ENTRY(proc_cache, idx);

		if (streq(proc_typep->proc_type_namep, procnamep)) 
			return(proc_typep);
	}

	/*
	 * Dlopen the proc module library
	 */
	sprintf(symname, "proc_type_%s", procnamep);

	proc_typep = (proc_type_t *)dlopen_lib(procnamep, symname, libname);
	if (proc_typep == NULL)
		return (proc_type_t *)0;

	/*
	 * Make sure the name in the proc_type_name matches the device 
	 */
	if (!streq(proc_typep->proc_type_namep, procnamep)) {
		printf("\nERROR: name does not match. %s != %s in %s:%s\n",
		       proc_typep->proc_type_namep, procnamep, libname, symname);
		return (proc_type_t *)0;
	}
		
	/* 
	 * Found a matching library, add it to the loaded list.
	 * 
	 * NOTE: We do not dlclose() the libarary here as we know it will be
	 * needed later on when the functions in the proc_typep are accessed.
	 */
	LIST_ADD_PTR(proc_cache, proc_type_t , proc_typep);

	return(proc_typep);
}

	/*
	 * Insert a config device into the address map of a certain domain
	 * Returns NULL on success, the overlapping device node on failure.
	 */

config_dev_t *
insert_domain_device(domain_t * domainp, config_dev_t * config_devp)
{
	config_dev_t **pdp, *dp;

	config_devp->domainp = domainp;
	config_devp->device_id = domainp->device.count;

	ASSERT(config_devp->addrp->topaddr > config_devp->addrp->baseaddr);
	ASSERT(config_devp->addrp->range != (tvaddr_t)0 );

		/* insert into sorted list of devices ... */
	for (pdp = &(domainp->device.listp); (dp=*pdp)!=(config_dev_t*)0; pdp=&(dp->nextp) ) {
		if (dp->addrp->topaddr <= config_devp->addrp->baseaddr) continue;
		if (dp->addrp->baseaddr >= config_devp->addrp->topaddr) break;

		return dp;
	}

		/* link device into list in order */
	config_devp->nextp = *pdp;
	*pdp = config_devp;
	domainp->device.count ++;
	PRINTF(("insert_domain_device %p name=<%s> base=%p, top=%p\n",
	       config_devp, config_devp->dev_typep->dev_type_namep,
	       config_devp->addrp->baseaddr,
	       config_devp->addrp->topaddr));
	return NULL;
}

/*
 * Insert a config address into the address map of a certain domain
 * Returns NULL on success, the overlapping device node on failure.
 */

config_addr_t *
insert_domain_address(domain_t * domainp, config_dev_t * devp, tpaddr_t baseaddr, tpaddr_t topaddr)
{
	config_addr_t **pap, *ap, *addrp;

	addrp = Xmalloc( sizeof(config_addr_t) );

	addrp->config_devp = devp;
	addrp->baseaddr = baseaddr;
	addrp->topaddr = topaddr;
	/* save us annoying adds and subs all the time */
	addrp->range = topaddr - baseaddr;

	if (devp->addrp == NULL)
		devp->addrp = addrp;

	/* insert into sorted list of addresses ... */
	for (pap = &(domainp->address.listp); (ap=*pap) != NULL; pap =&(ap->nextp) ) {
		if (ap->topaddr <= addrp->baseaddr) continue;
		if (ap->baseaddr >= addrp->topaddr) break;
		return ap;
	}

	/* link device into list in order */
	addrp->nextp = *pap;
	*pap = addrp;
	domainp->address.count++;
	DBG( PRINTF(("insert_domain_address devp=%p addrp=%p base=%llx, "
		"top=%llx\n",
		addrp->config_devp, addrp, addrp->baseaddr,
		addrp->topaddr)); );
	return NULL;
}

